package com.flyxiaozhu.erp.core.libs;

import org.apache.commons.lang.StringUtils;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;

import javax.persistence.criteria.*;
import javax.servlet.http.HttpServletRequest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SearchSpecification<T> implements Specification<T> {
    private final String SEARCH_PREFIX = "search_";

    private HttpServletRequest request;

    private Map<String, SearchSpecFilter> filters = new HashMap<>();

    private SearchBeforeSpecFilter beforeSpecFilter = null;

    public SearchSpecification(HttpServletRequest request) {
        this.request = request;
    }

    public void setBeforeFilter(SearchBeforeSpecFilter filter) {
        this.beforeSpecFilter = filter;
    }

    public void setFilter(String filed, SearchSpecFilter filter) {
        filters.put(filed, filter);
    }

    @Nullable
    @Override
    @SuppressWarnings("unchecked")
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
        List<Predicate> predicates = new ArrayList<>();
        Map<String, String[]> params = request.getParameterMap();

        Object begin = null, end = null;
        if (beforeSpecFilter != null) {
            beforeSpecFilter.process(predicates, root, criteriaQuery, cb);
        }

        for (String name : params.keySet()) {
            if (StringUtils.isBlank(params.get(name)[0]) || name.indexOf(SEARCH_PREFIX) != 0) {
                continue;
            }

            String[] parts = name.split("_");
            if (parts.length < 2) {
                continue;
            }

            String value = StringUtils.trim(params.get(name)[0]);

            String op;
            String filed;
            if (parts.length == 2) {
                op = "EQ";
                filed = StringUtils.trim(parts[1]);
            } else {
                op = StringUtils.trim(parts[1]);
                filed = StringUtils.trim(parts[2]);
            }

            Object v = value;
            if (parts.length >= 4) {
//                v = transformValue(value, parts[3]);
                if (v == null) {
                    continue;
                }
                switch (parts[3].toLowerCase()) {
                    case "begin":
                        begin = v;
                        break;
                    case "end":
                        end = v;
                        break;
                }
            }

            Predicate predicate = null;
            if (filters.containsKey(name)) {
                predicate = filters.get(name).process(predicates, filed, op, value, root, criteriaQuery, cb);
            } else {
                try {
                    Path path = root.get(filed);
                    switch (op.toUpperCase()) {
                        case "EQ":
                            predicate = cb.equal(path, v);
                            break;
                        case "LIKE":
                            predicate = cb.like(path, "%" + v + "%");
                            break;
                        case "GT":
                            predicate = cb.greaterThan(path, (Comparable) v);
                            break;
                        case "LT":
                            predicate = cb.lessThan(path, (Comparable) v);
                            break;
                        case "GTE":
                            predicate = cb.greaterThanOrEqualTo(path, (Comparable) v);
                            break;
                        case "LTE":
                            predicate = cb.lessThanOrEqualTo(path, (Comparable) v);
                            break;
                        case "BW":
                            if (begin != null && end != null) {
                                predicate = cb.between(path, (Comparable) begin, (Comparable) end);
                            } else if (begin != null) {
                                predicate = cb.greaterThanOrEqualTo(path, (Comparable) begin);
                            } else {
                                predicate = cb.lessThanOrEqualTo(path, (Comparable) end);
                            }
                            break;
                    }
                } catch (Exception e) {
                    continue;
                }

            }

            if (predicate != null) {
                predicates.add(predicate);
            }
        }

        if (predicates.size() > 0) {
            return cb.and((Predicate[]) predicates.toArray(new Predicate[predicates.size()]));
        }

        return null;
    }

    private Object transformValue(String value, String type) {
        try {
            switch (type.toLowerCase()) {
                case "date":
                case "time":
                    String pattern;
                    if (value.length() >= 19) {
                        pattern = "yyyy-MM-dd HH:mm:ss";
                    } else {
                        pattern = "yyyy-MM-dd";
                    }
                    SimpleDateFormat sdf = new SimpleDateFormat(pattern);
                    return sdf.parse(value);
            }
        } catch (ParseException e) {
        }
        return null;
    }
}
